"""
Analytical Geometry
===
包括了点及其运算
"""
import numpy as np
import math


def dot(a, b):
    """
    点积
    ===
    返回A和B之间的点积.
    可以是点和点, 向量和点, 点和向量, 点和复数, 复数和点, 向量和向量, 向量和复数, 复数和向量, 复数和复数
    向量可以是任何非字符串的可叠代类型.
    """
    if isinstance(a, Point) and isinstance(b, Point):  # 点和点
        if a.di == b.di:
            return sum(a.cood[i] * b.cood[i] for i in range(a.di))
        else:
            raise DimentionalException('维数不匹配')
    if isinstance(a, Point) and type(b) == complex:  # 点和复数
        if a.di == 2:
            return a.x * b.real + a.y * b.imag
        else:
            raise DimentionalException('维数不匹配')
    if type(a) == complex and type(b) == Point:  # 复数和点
        if a.di == 2:
            return a.real * b.x + a.imag * b.y
        else:
            raise DimentionalException('维数不匹配')
    if type(a) == complex and type(b) == complex:  # 复数和复数
        return a.real * b.real + a.imag * b.imag
    try:
        if len(b) == a.di and type(b) != str:  # 向量和点
            return sum(a.cood[i] * b[i] for i in range(a.di))
        elif type(b) == str:
            raise TypeError()
        else:
            raise DimentionalException()
    except DimentionalException:
        raise DimentionalException('维数不匹配')
    except:
        try:
            if len(a) == b.di and type(a) != str:  # 点和向量
                return sum(b.cood[i] * a[i] for i in range(b.di))
            elif type(a) == str:
                raise TypeError()
            else:
                raise DimentionalException()
        except DimentionalException:
            raise DimentionalException('维数不匹配')
        except:
            try:
                if len(a) == len(b) and type(a) != str and type(b) != str:  # 向量和向量
                    return sum(a[i] * b[i] for i in range(len(a)))
                elif type(a) == str or type(b) == str:
                    raise TypeError()
                else:
                    raise DimentionalException('维数不匹配')
            except DimentionalException:
                raise DimentionalException('维数不匹配')
            except:
                try:
                    if len(a) == 2 and type(b) == complex and type(a) != str:  # 向量和复数
                        return a[0] * b.real + a[1] * b.imag
                    elif type(a) == str:
                        raise TypeError()
                    else:
                        raise DimentionalException()
                except DimentionalException:
                    raise DimentionalException('维数不匹配')
                except:
                    try:
                        if len(b) == 2 and type(a) == complex and type(b) != str:  # 复数和向量
                            return b[0] * a.real + b[1] * a.imag
                        elif type(b) == str:
                            raise TypeError()
                        else:
                            raise DimentionalException()
                    except DimentionalException:
                        raise DimentionalException('维数不匹配')
                    except:
                        raise TypeError('非法类型')


def exp(power, returning=complex):
    """
    Exp
    返回一个实数,一个点, 一个列表或一个复数的e指数.
    默认返回complex, returning参数可控制返回类型, 不支持则报错.
    """
    if type(power) == float or type(power) == int:
        return math.exp(power)
    if type(power) == Point:
        if power.di != 2:
            raise DimentionalException("维数不为2")
        if returning == complex:
            return math.exp(power.x) * complex(math.cos(power.y), math.sin(power.y))
        if returning == Point:
            return Point(math.cos(power.y), math.sin(power.y)) * math.exp(power.x)
        if returning == list:
            return [math.cos(power.y) * math.exp(power.x), math.sin(power.y) * math.exp(power.x)]
        raise TypeError("不支持的类型")
    if type(power) == complex:
        if returning == complex:
            return math.exp(power.real) * complex(math.cos(power.imag), math.sin(power.imag))
        if returning == Point:
            return Point(math.cos(power.imag), math.sin(power.imag)) * math.exp(power.real)
        if returning == list:
            return [math.cos(power.imag) * math.exp(power.real), math.sin(power.imag) * math.exp(power.real)]
        raise TypeError("不支持的类型")
    if type(power) == list:
        if len(power) != 2:
            raise DimentionalException("维数不为2")
        if returning == complex:
            return math.exp(power[0]) * complex(math.cos(power[1]), math.sin(power[1]))
        if returning == Point:
            return Point(math.cos(power[1]), math.sin(power[1])) * math.exp(power[0])
        if returning == list:
            return [math.cos(power[1]) * math.exp(power[0]), math.sin(power[1]) * math.exp(power[0])]
    raise TypeError("不支持的类型")


class DimentionalException(Exception):
    """
    维数错误
    ===
    """
    pass


class Point:
    """
    点
    ===

    cood为该点的坐标,
    Point(1,0)即坐标为[1,0]
    Point(1,4,-3)即坐标为[1,4,-3]
    输入复数可以创建复平面上的点
    """

    def __init__(self, *cood):
        arr = []
        for ele in cood:
            arr.append(ele)
        if len(arr) == 1:
            if type(arr[0]) == complex:
                self.__cood = np.asarray([arr[0].real, arr[0].imag])
                self.__di = 2
            else:
                self.__cood = np.asarray([arr[0]])
                self.__di = 1
        else:
            self.__cood = np.asarray(arr)
            self.__di = len(cood)

    @property
    def di(self):
        return self.__di

    @di.setter
    def _di(self, value):
        self.__di = value

    @property
    def cood(self):
        """
        返回点的坐标.
        在类内部调用时维度可以改变, 在类外则不能改变
        """
        return self.__cood

    @cood.setter
    def _cood(self, value):
        self.__cood = value

    @property
    def x(self):
        """
        返回该点的x坐标.
        若为零维点抛出错误.
        """

        if self.di == 0:
            raise DimentionalException('维数过少')
        return self.cood[0]

    @property
    def y(self):
        """
        返回该点的y坐标.
        若小于等于1维抛出错误.
        """
        if self.di <= 1:
            raise DimentionalException('维数过少')
        return self.cood[1]

    @property
    def z(self):
        """
        返回该点的z坐标.
        若小于等于2维抛出错误.
        """
        if self.di <= 2:
            raise DimentionalException('维数过少')
        return self.cood[2]

    def broadcast(self, targetDimention):
        """
        Point.broadcast(n)
        ===
        将点广播或者投影至n维.
        """
        if targetDimention < 1 or type(targetDimention) != int:
            raise DimentionalException('维数不合法')
        ans = []
        for i in range(targetDimention):
            if i >= self.di:
                ans.append(0)
                continue
            ans.append(self.cood[i])
        self.__cood = np.asarray(ans)
        self.__di = targetDimention

    def broadcasted(self, targetDimention):
        """
        Point.broadcasted(n)
        返回该点广播或者投影至n维的点.
        """
        if targetDimention < 1 or type(targetDimention) != int:
            raise DimentionalException('维数不合法')
        ans = []
        for i in range(targetDimention):
            if i >= self.di:
                ans.append(0)
                continue
            ans.append(self.cood[i])
        return Point(*ans)

    @cood.setter
    def cood(self, newcood):
        """
        设置该点点坐标
        """
        if len(newcood) != self.di:
            raise DimentionalException(
                '维数不匹配:修改后的维数为{}, 而先前的维数为{}. '.format(len(newcood), self.di))
        arr = []
        for ele in newcood:
            arr.append(ele)
        self.__cood = np.asarray(arr)

    @property
    def comp(self):
        """
        获得点的复数形式
        """
        if self.di > 2:
            raise DimentionalException('大于二维的点')
        if self.di == 1:
            return self.x
        if self.di == 0:
            return 0
        return complex(self.x, self.y)

    @property
    def arg(self):
        """
        返回点的幅角
        """
        if self.di > 2:
            raise DimentionalException('暂不支持大于二维的点')
        if self.di == 1:
            return 0
        if self.di == 0:
            return None
        return math.atan(self.y, self.x)
    """
    重载运算符
    包括str, 取负, 绝对值, 加、减、数乘, 数除, 等于
    """

    def __str__(self):
        return (str(self.cood))

    def __neg__(self):
        return Point(*(-i for i in self.cood))

    def __abs__(self):
        return sum(i ** 2 for i in self.cood)**(1 / 2)

    def __add__(self, other):
        """
        加法
        返回点.
        """
        if isinstance(other, Point) == False:
            tp = type(other)
            if tp == int or tp == float or tp == np.float64:  # 点加实数, 返回点
                if self.di == 0:
                    raise DimentionalException("零维的点无法作为被加数.")
                return Point(*(i + other for i in self.cood))
            if tp == complex:  # 点减复数, 返回点, 若不是二维抛出错误
                if self.di == 2:
                    return Point(self.x + other.real, self.y + other.imag)
                raise DimentionalException('只有二维点可以和复数进行运算')
            if tp == str:  # 点加字符串不定义
                return NotImplemented
            # 输入的是数组
            try:
                np.asarray(other)
            except Exception:
                return NotImplemented
            finally:
                if np.asarray(other).shape != np.asarray(self.cood).shape:
                    raise DimentionalException('维数不匹配:{}和{}'.format(
                        type(other), self.cood))
                return np.asarray([self.cood[i] + other[i] for i in range(self.di)])
        # 如果加上点或点的子类, 返回点.
        if self.di != other.di:
            raise DimentionalException('维数不匹配')
        ans = [self.cood[i] + other.cood[i] for i in range(self.di)]
        return Point(*ans)

    def __radd__(self, other):
        """
        右加法
        原则上返回左侧的类型, 若左侧是实数返回点, 若左侧是数组返回numpy数组.注意, numpy数组不能作为被加数!
        """
        if type(other) != Point:
            tp = type(other)
            if tp == int or tp == float or tp == np.float64:  # 实数减点, 返回点
                if self.di == 1:
                    return other + self.x
                if self.di == 0:
                    return other
                return Point(*(other + i for i in self.cood))
            if tp == complex:  # 复数减点, 返回复数, 若点不是二维抛出错误
                if self.di == 2:
                    return complex(other.real + self.x, other.imag + self.y)
                raise DimentionalException('只有二维点可以和复数进行运算')
            if tp == str:  # 字符串减点不定义
                return NotImplemented
            # 输入的是数组
            try:
                np.asarray(other)
            except Exception:
                return NotImplemented  # 如果不是数组返回NotImplemented
            finally:
                if np.asarray(other).shape != np.asarray(self.cood).shape:
                    raise DimentionalException('维数不匹配')
                return np.asarray([other[i] + self.cood[i] for i in range(self.di)])
        # 点加上点. 若是点的子类由子类的左加法定义.
        if self.di != other.di:
            raise DimentionalException('维数不匹配')
        ans = [other.cood[i] + self.cood[i] for i in range(self.di)]
        return Point(*ans)

    def __sub__(self, other):
        """
        减法
        返回点.
        """
        if isinstance(other, Point) == False:
            tp = type(other)
            if tp == int or tp == float or tp == np.float64:  # 点减实数, 返回点
                if self.di == 0:
                    raise DimentionalException("零维的点无法作为被减数.")
                return Point(*(i - other for i in self.cood))
            if tp == complex:  # 点减复数, 返回点, 若不是二维抛出错误
                if self.di == 2:
                    return Point(self.x - other.real, self.y - other.imag)
                raise DimentionalException('只有二维点可以和复数进行运算')
            if tp == str:  # 点减字符串不定义
                return NotImplemented
            # 输入的是数组
            try:
                np.asarray(other)
            except Exception:
                return NotImplemented  # 如果不是数组返回NotImplemented
            finally:
                if np.asarray(other).shape != np.asarray(self.cood).shape:
                    raise DimentionalException('维数不匹配')
                return Point(*[self.cood[i] - other[i] for i in range(self.di)])
        # 如果减去点或点的子类, 返回点.
        if self.di != other.di:
            raise DimentionalException('维数不匹配')
        ans = [self.cood[i] - other.cood[i] for i in range(self.di)]
        return Point(*ans)

    def __rsub__(self, other):
        """
        右减法
        原则上返回左侧的类型, 若左侧是实数返回点, 若左侧是数组返回numpy数组. 注意, numpy数组不能作为被减数!
        """
        if type(other) != Point:
            tp = type(other)
            if tp == int or tp == float or tp == np.float64:  # 实数减点, 返回点
                if self.di == 1:
                    return other - self.x
                if self.di == 0:
                    return other
                return Point(*(other - i for i in self.cood))
            if tp == complex:  # 复数减点, 返回复数, 若点不是二维抛出错误
                if self.di == 2:
                    return complex(other.real - self.x, other.imag - self.y)
                raise DimentionalException('只有二维点可以和复数进行运算')
            if tp == str:  # 字符串检点不定义
                return NotImplemented
            # 输入的是数组
            try:
                np.asarray(other)
            except Exception:
                return NotImplemented  # 如果不是数组返回NotImplemented
            finally:
                if np.asarray(other).shape != np.asarray(self.cood).shape:
                    raise DimentionalException('维数不匹配')
                return np.asarray(other[i] - self.cood[i] for i in range(self.di))
        # 点减去点. 若是点的子类由子类的左减法定义.
        if self.di != other.di:
            raise DimentionalException('维数不匹配')
        ans = [other.cood[i]-self.cood[i] for i in range(self.di)]
        return Point(*ans)

    def __mul__(self, other):
        # 数乘
        if type(other) != int and type(other) != float and type(other) != np.float64:
            return NotImplemented
        return Point(*(i * other for i in self.cood))

    def __rmul__(self, other):
        # 若前者是复数返回其复数和左边复数的乘积, 若是实数返回数乘, 若点是一维返回数字, 若是矩阵返回点, 否则NotImplemented. 注意ndarray不能作为被乘数!
        if type(other) == complex:
            return other * self.comp
        if type(other) == float or type(other) == int or type(other) == np.float64:
            if self.di == 0:
                return 0
            if self.di == 1:
                return other * self.x
            return Point(*(i * other for i in self.cood))
        # 矩阵乘法:
        if type(other) == list:
            mat = np.asarray(other)
            if len(mat.shape) != 2:
                if len(mat.shape) == 1:
                    rownum = 1
                    colnum = mat.shape[0]
                else:
                    raise DimentionalException("输入的数组不为矩阵!")
            else:
                rownum = mat.shape[0]
                colnum = mat.shape[1]
            if colnum != self.di:
                raise DimentionalException("矩阵不合法!")
            ans = []
            for i in range(rownum):
                temp = 0
                for j in range(colnum):
                    if len(mat.shape) != 1:
                        temp += mat[i][j] * self.cood[j]
                    else:
                        temp += mat[j] * self.cood[j]
                ans.append(temp)
            if len(ans) == 1:
                return Point(7)
            return Point(*ans)

        return NotImplemented

    def __truediv__(self, other):
        """
        除法
        仅定义数除的逆运算
        """
        if type(other) == int or type(other) == float or type(other) == np.float64:
            return Point(*[self.cood[i] / other for i in range(self.di)])
        return NotImplemented

    def __rtruediv__(self, other):
        """
        右除法
        仅定义复数对二维点的除法
        """
        if type(other) == complex:
            return other/self.comp
        return NotImplemented

    def __eq__(self, other):
        """
        等于, 可以是点, 复数, 向量.
        """
        if type(other) == Point:
            return self.cood.tolist() == other.cood.tolist()
        if type(other) == list or type(other) == np.ndarray:
            try:
                for i in range(len(other)):
                    if self.cood[i] != other[i]:
                        return False
                return True
            except Exception:
                return False
        if type(other) == complex and self.di == 2:
            return self.x == other.real and self.y == other.imag
        return False


# Only for debug
if __name__ == '__main__':
    print(exp([1/2, 3 ** (1/2) / 2]))
